<?php
declare (strict_types=1);

namespace app\admin\command\make;

use ReflectionClass;
use think\db\Query;
use think\facade\Db;
use think\helper\Arr;
use think\helper\Str;
use think\console\Input;
use think\console\Output;

class Model extends \think\console\command\make\Model
{
    /**
     * 要生成模型的表字段信息
     * @var array
     */
    private $fields = [];


    /**
     * 数据表名
     * @var string
     */
    private $tableName;


    /**
     * 模型名称，大驼峰类名
     * @var string
     */
    private $modelName;

    /**
     * 表信息
     * @var array
     */
    private $tableInfo;

    /**
     * 表主键名称
     * @var string
     */
    private $pk;

    /**
     * db类
     * @var Query
     */
    protected $db;

    /**
     * 生成文件路径
     * @var string
     */
    protected $filePath;

    /**
     * 类命名空间
     * @var string
     */
    protected $namespace;


    protected function execute(Input $input, Output $output)
    {
        $name = trim($input->getArgument('name'));

        $className = $this->getClassName($name);

        $this->filePath = $this->getPathName($className);

        $this->namespace = trim(implode('\\', array_slice(explode('\\', $className), 0, -1)), '\\');
        $this->modelName = str_replace($this->namespace . '\\', '', $className);
        if ($this->modelName !== Str::studly($this->modelName)) {
            throw new \Exception("模型命名'$this->modelName'不符合类名大驼峰规范");
        }
        $this->db = Db::name($this->modelName);
        if (is_file($this->filePath)) {
            $this->updateDoc($className);
            $output->writeln('<info>' . $this->type . ':' . $className . ' updated successfully.</info>');
            return false;
        }

        if (!is_dir(dirname($this->filePath))) {
            mkdir(dirname($this->filePath), 0755, true);
        }

        file_put_contents($this->filePath, $this->buildClass($className));

        $output->writeln('<info>' . $this->type . ':' . $className . ' created successfully.</info>');
    }

    /**
     * 类已存在，更新文档
     * @param $className
     * @throws \ReflectionException
     */
    protected function updateDoc($className)
    {
        $refClass = new ReflectionClass($className);
        $doc = $refClass->getDocComment();
        $fields = $this->getFields()->template();
        $header = [];
        $body = [];
        $footer = [];
        $propertyData = [];
        if ($doc) {
            $docArr = explode("\n", $doc);
            foreach ($docArr as $item) {
                if ($item === '/**') {
                    continue;
                }
                if ($item === ' */') {
                    continue;
                }
                $data = strstr($item, '@property');
                if ($data === false) {
                    if (count($body) === 0) {
                        $header[] = $item;
                    } else {
                        $footer[] = $item;
                    }
                } else {
                    preg_match('/\$[A-Za-z_]+\w*/', $data, $matches);
                    if (isset($matches[0]) && isset($fields[$matches[0]])) {
                        $data = $fields[$matches[0]];
                        unset($fields[$matches[0]]);
                    }
                    $body[] = $data;
                }
            }
        }
        $footer[] = ' */';
        $this->arrayPush($propertyData, $header);
        $this->arrayPush($propertyData, $body);
        $this->arrayPush($propertyData, $fields);
        $this->arrayPush($propertyData, $footer);
        $replaceDoc = implode("\n", $propertyData);
        $stub = file_get_contents($this->filePath);
        if (!$doc) {
            $classStr = "class $this->modelName";
            $headerStr = strstr($stub, $classStr, true);
            if (!$headerStr) {
                throw new \Exception("$className 类文件格式不正确");
            }
            $doc = $headerStr;
            $replaceDoc = $headerStr . $replaceDoc . "\n";
        }
        $str = str_replace([$doc], [$replaceDoc], $stub);
        file_put_contents($this->filePath, $str);
    }

    protected function arrayPush(&$arr, array $args) {
        foreach ($args as $arg) {
            $arr[] = $arg;
        }
    }

    protected function buildClass(string $name): string
    {
        $replaceMap = $this->getReplaceMap();
        $keys = [];
        $values = [];
        foreach ($replaceMap as $key => $item) {
            $keys[] = $key;
            $values[] = $item;
        }
        $stub = file_get_contents($this->getStub());
        return str_replace($keys, $values, $stub);
    }

    protected function getReplaceMap()
    {

        $property = implode("\n", $this->getFields()->template());
        return [
            '{%className%}' => $this->modelName,
            '{%namespace%}' => $this->namespace,
            '{%app_namespace%}' => $this->app->getNamespace(),
            '{%property%}' => $property,
            '{%tableComment%}' => $this->getTableInfo()['table_comment'],
            '{%createTime%}' => date('Y-m-d H:i:s'),
            '{%pk%}' => $this->getPk()
        ];
    }

    protected function getPk()
    {
        $this->pk = $db = $this->db->getPk();
        if (empty($this->pk) || $this->pk === 'id') {
            return '';
        }
        return 'protected $pk = \'' . $this->pk . '\';';
    }

    protected function getStub(): string
    {
        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
    }

    /**
     * 获取数据表的字段信息
     * @return $this
     */
    protected function getFields()
    {
        if (count($this->fields)) {
            return $this;
        }
        $this->fields = $this->db->getFields();
        return $this;
    }

    protected function getTableInfo()
    {
        $this->tableName = $this->db->getConfig('prefix') . Str::snake($this->modelName);
        $result = Db::query("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=:db AND TABLE_NAME=:tablename", ['db' => $this->db->getConfig('database'), 'tablename' => $this->tableName]);
        $this->tableInfo = array_change_key_case(Arr::first($result));
        return $this->tableInfo;
    }

    protected function template()
    {
        $fieldTemp = " * @property :date_type $:name :comment  默认值 :default";
        $this->fields = array_map(function ($item) {
            $item['date_type'] = preg_replace([
                '/.*int.*/', '/.*char.*/', '/text/', '/timestamp/', '/.*decimal.*/'
            ],
                ['int', 'string', 'string', 'string', 'float'], $item['type']);
            return $item;
        }, $this->fields);
        $fields = [];
        foreach ($this->fields as $key => $item) {
            if (!$item['default']) {
                $itemFieldTemp = str_replace(["默认值", ":default"], "", $fieldTemp);
            } else {
                $itemFieldTemp = $fieldTemp;
            }
            $fields["\${$key}"] = $this->bindParams($itemFieldTemp, $item);
        }
        return $fields;
    }

    /**
     * 参数绑定 模板参数替换
     *
     * @param string $str
     * @param array $bind
     * @return string
     * @author 秋月 414111907@qq.com
     */
    protected function bindParams(string $str, array $bind = []): string
    {
        foreach ($bind as $key => $value) {
            if (is_array($value)) {
                $value = implode("\n", $value);
            }
            $str = str_replace(':' . $key, $value, $str);
        }
        return $str;
    }
}
